/**
 * Promise 的手动实现
 */

const PENDING = 'pending'
const FULFILL = 'filled'
const REJECTED = 'rejected'


// 工具函数， 用来处理 Promise 的异常捕获
function util (execFunc, value, resolved, reject) {
    try {
        const val = execFunc(value)
        resolved(val)
    } catch (e) {
        reject(e)
    }
}

class MyPromise {
    // 在调用函数的时候就会出发传进的函数, constructor 是初始化的化函数
    constructor(executor) {
        this.state = PENDING
        this.value = undefined
        this.reason = undefined
        // 保存成功的回调函数
        this.onFulfilled = []
        // 保存失败的回调函数
        this.onRejected = []

        const resolve = (value) => {
            if (this.state === PENDING) {
                // this.state = FULFILL
                // this.value = value
                // 上面的函数可以转换状态， 拿到 Promise 被兑换的值， 但是如果有 then 方法， 也需要进行执行， 他传入的是回调函数
                // this.onFulfilled()
                // 采用临时的方法， setTimeout 去执行, 这并不是一个好的设计， 真正执行到这一步的时候， 其实是一个微任务， 但是 setTimeout 是一个宏任务
                // setTimeout(() => this.onFulfilled(this.value), 0)
                // 使用 queueMicrotasks() 方法， 构造一个微任务
                // queueMicrotask(()=> this.onFulfilled(this.value))
                // 处理连续调用的 then 方法的情况
                // ===========
                // queueMicrotask(() => { // 注意： 现在的状态锁定都放在了微任务中， 当调用 then 方法时， 他的参数是不固定的， 很有可能 resolve 和 reject 可以多次执行， 因为状态都还没变， 都是 pending
                //     // 这时我们需要做判断， 如果状态是锁死的， 我们不做任何处理
                //     if (this.state !== PENDING) return
                //     this.state = FULFILL
                //     this.value = value
                //     this.onFulfilled.forEach(fn => fn(this.value))
                // })
                // 我们还需要处理的是， 如果传入的值是以个 promise 我们怎么处理，以及传入的是一个 thenable 函数
                // 1. 如果传入的就是一个 Promise 对象， 我们直接以他的值作为回调函数的值
                
                if (value instanceof MyPromise) {
                    value.then(resolve, reject) // 把当前的 resolve 和 reject 传入， 当函数执行的时候，会将值传递到当前的 resolve 和 reject 中 
                    return 
                }

                // 2. 如果传入的是 thenable 函数
                // 检测 value 是否是一个 thenable
                if (value !== null && (typeof value === "object" || typeof value === "function")) {
                    let then 
                    try {
                        then = value.then
                    } catch (error) {
                        reject(error)
                        return
                    }
                }

                // 如果 then 是以个函数， 可以认为 value 是一个 thenable 对象
                if (typeof then === 'function') {
                    let called = false // 避免多次调用， 按要求这里面的方法只会执行一次，并修改他的状态
                    try {
                        then.call(value, (y) => {
                            if (called) return
                            called = true
                            resolve(y)
                        }, (r) => {
                            if (called) return
                            called = true
                            reject(r)
                        })
                        
                    } catch (error) {
                        reject(error)
                    }
                    return 
                }
                queueMicrotask(() => { // 注意： 现在的状态锁定都放在了微任务中， 当调用 then 方法时， 他的参数是不固定的， 很有可能 resolve 和 reject 可以多次执行， 因为状态都还没变， 都是 pending
                    // 这时我们需要做判断， 如果状态是锁死的， 我们不做任何处理
                    if (this.state !== PENDING) return
                    this.state = FULFILL
                    this.value = value
                    this.onFulfilled.forEach(fn => fn(this.value))
                })

            }
        }

        const reject = (reason) => {
            if (this.state === PENDING) {
                // this.state = REJECTED
                // this.reason = reason
                // this.onRejected() // 显然这种方式不能解决 Promise 的链式调用, 因为 then 方法， 一定会在 new 之后， 但是这个时候 then 的方法并没有， 会执行 undefined.onRejected, 会报错
                // setTimeout(() => this.onRejected(this.reason), 0)
                // queueMicrotask(() => this.onRejected(reason))
                // 但是我们知道， then 方法是链式调用的， 他可以一直执行 then 方法， 可以传很多回调函数， 我们采用数组的方式将每一次的回调函数传入， 并且在执行 resolve 等方法的时候依次调用他们
                if (reason instanceof MyPromise) {
                    reason.then(resolve, reject) // 把当前的 resolve 和 reject 传入， 当函数执行的时候，会将值传递到当前的 resolve 和 reject 中 
                    return 
                }

                // 2. 如果传入的是 thenable 函数
                // 检测 value 是否是一个 thenable
                if (reason !== null && (typeof reason === "object" || typeof reason === "function")) {
                    let then 
                    try {
                        then = reason.then
                    } catch (error) {
                        reject(error)
                        return
                    }
                }

                // 如果 then 是以个函数， 可以认为 value 是一个 thenable 对象
                if (typeof then === 'function') {
                    let called = false // 避免多次调用， 按要求这里面的方法只会执行一次，并修改他的状态
                    try {
                        then.call(reason, (y) => {
                            if (called) return
                            called = true
                            resolve(y)
                        }, (r) => {
                            if (called) return
                            called = true
                            reject(r)
                        })
                        
                    } catch (error) {
                        reject(error)
                    }
                    return 
                }


                queueMicrotask(() => {
                    if (this.state !== PENDING) return 
                    this.state = REJECTED
                    this.reason = reason
                    this.onRejected.forEach(fn => fn(this.reason))
                })
            }
        }
        try {
            executor(resolve, reject) // 要确保有这两个函数
        } catch (error) {
            reject(error)
        }       
    }

    // 根据 MDN 对 Promise 的描述， catch 方法就是 then 的语法糖， 他的实现就是对于 then 的封装， Promise.prototype.then(undefined, onRejected))
    catch(onRejected) {
        return this.then(undefined, onRejected)
    }

    // finally, 用于注册一个在 promise 敲定（兑现或则失败）时调用的函数， 他会立即返回一个等效的 Promise 对象， 可以让我们调用其他的 promise 方法
    /**
     等效Promise指的是两个Promise对象在行为上是相同的，
        即它们的状态（fulfilled、rejected）和值（或错误）是相同的。
        这意味着如果你有一个Promise P，另一个Promise Q是等效于P的，那么无论P是成功还是失败，Q都会以相同的方式成功或失败。

     then方法返回的新Promise是指
        当你调用一个Promise的.then()方法时，这个方法会返回一个新的Promise对象。
        这个新的Promise对象会根据原Promise对象的状态和.then()方法中提供的处理函数来决定自己的状态和值。
        如果原Promise成功，.then()方法中的成功处理函数会被调用，其返回值（或抛出的错误）将决定新Promise的状态和值；
        如果原Promise失败，.then()方法中的失败处理函数会被调用，其返回值（或抛出的错误）将决定新Promise的状态和值。
     */
    finally (onFinally) {
        return this.then(() => onFinally(), () => onFinally())
    }

    // Promise 的 then 方法
    then(onFulfilled, onRejected) {
        // 上面的问题， 我们已经解决如何处理多个 then 调用的问题了， 但是， 我们在想如果他在定时器中被调用， 会不会执行， 定时器是宏任务， 他会在微任务之后执行， 我们采取补票的方法， 解决这个问题
        // if (this.state === FULFILL && onFulfilled) { // 说明状态已经被改变了, 如果状态改变后再进行调用， 这个时候， 可以再执行回调函数的方法
        //     onFulfilled(this.value)
        // }

        // if (this.state === REJECTED && onRejected) {
        //     onRejected(this.reason)
        // }

        // if (this.state === PENDING) { 
        //     this.onFulfilled.push(onFulfilled)
        //     this.onRejected.push(onRejected)
        // }

        // 实现当 onRejected 为 undefined 时将错误传递出去， 只需要抛出错误即可， 错误会被 try...catch 捕获从而被抛出
        const defaultRejected = (err) => { throw err } // 如果没有传递这个方法， 也可以抛出错误， 在工具函数中会被处理

        onRejected = onRejected || defaultRejected

        const defaultFulfilled = (value) => value 

        onFulfilled = onFulfilled || defaultFulfilled

        // 根据 then 方法的描述， 我们可以再使用以个 Promise 进行包裹， 返回他的值
        return new MyPromise((resolve, reject) => {
            // 状态已经锁定， 再调用 then 方法
            if (this.state === FULFILL && onFulfilled) {
                util(onFulfilled, this.value, resolve, reject)
            }

            if (this.state === REJECTED && onRejected) {
                util(onRejected, this.reason, resolve, reject)
            }

            // pending 状态
            if (this.state === PENDING) {
                onFulfilled && this.onFulfilled.push((value) => {
                    util(onFulfilled, value, resolve, reject)
                })
                onRejected && this.onRejected.push((reason) => {
                   util(onRejected, reason, resolve, reject)
                })
            }
        })
        
    }
}

// 注意： 如果以状态为主， 需要将改变的状态的方法放进微任务中，为什么？ 因为前面的代码执行是同步的， 会先执行， 状态一旦改变， 就不会进入 PENDING 状态，导致拿不到想要的值

const p = new MyPromise((resolve, reject) => {
    const newp = new MyPromise((resolve, reject) => {
        resolve('success')
    })
    reject(newp) // 根据内部实现可以知道后序处理是返回一个 resolve 成功结果
})
p.then((res) => {
    console.log('🚀 ~ This is a result of console.log ~ ✨: ', res);
}).catch(err => {
    console.log('🚀 ~ This is a result of console.log ~ ✨:=== ', err);
})
// .then(res => {
//     console.log(res, 'res==');
// }).then(res => {
//     console.log('🚀 ~ This is a result of console.log ~ ✨: ==', res);
// })

// p.then(res => {
//     console.log(res, 'res2');
// })

// 采用补票的方式执行这个 then 方法
// setTimeout(() => {
//     p.then(res => {
//         console.log(res, 'res3');
//     })
// })



/** 
    then 方法的定义： 
        1. 必须返回一个 Promise 对象
        2. 如果返回的是一个普通值， 那么就用这个值包装成一个 Promise 对象进行返回
        3. 如果没有 return 语句， 那么就返回一个 undefined 包装的 Promise 对象
        4. 如果 then 方法中出现异常， 则调用失败方法， 跳转到下一个 then 的 onRejected 方法
        5. 如果 then 方法中没有传入任何回调， 那么继续向下传递（值穿透）
        6. 如果返回的值是一个 Promise 对象， 那么以这个对象为准， 返回他的结果
 */
